﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using King.Engine.Components;

namespace King.Engine.GameObjects
{

    /// <summary>
    /// An updatable and drawable object in within the game
    /// </summary>
    public abstract class GameObject
    {

        #region Fields
        
        private List<Component> _components;
        private List<Component> _newComponents;
        private List<Component> _removingComponents;

        private bool _componentsChanged;

        #endregion


        #region Properties

        /// <summary>
        /// Set whether or not this gameobject should be updated
        /// </summary>
        public bool UpdateEnabled
        {
            set
            {
                Update = value ? (Action<GameTime>) UpdateAll : UpdateNone;
            }
        }

        /// <summary>
        /// Set whether or not this gameobject should be drawn
        /// </summary>
        public bool DrawEnabled
        {
            set
            {
                Draw = value ? (Action) DrawAll : DrawNone;
            }
        }

        /// <summary>
        /// The location and size of this object, in world coordinates
        /// </summary>
        public Rectangle Bounds;

        /// <summary>
        /// The bounds of this object from the previous update
        /// </summary>
        public Rectangle PreviousBounds;

        /// <summary>
        /// True if this object should not be deleted when the current activity is cleared
        /// </summary>
        public bool IsPersistant;

        /// <summary>
        /// The location of this object, in world coordinates
        /// </summary>
        public Vector2 Location
        {
            get
            {
                return new Vector2(Bounds.X, Bounds.Y);
            }
            set
            {
                Bounds.Location = new Point((int)value.X, (int)value.Y);
            }
        }

        internal Action<GameTime> Update
        {
            get;
            private set;
        }

        internal Action Draw
        {
            get;
            private set;
        }

        #endregion


        #region Constants
        
        /// <summary>
        /// True if this is a GUI element
        /// </summary>
        protected internal readonly bool IsGUI;

        #endregion


        #region Constructor

        protected GameObject()
            :this(false)
        {
        }

        protected GameObject(bool isGUI)
            : this(isGUI, true, true)
        {
        }

        protected GameObject(bool isGUI, bool updateEnabled, bool drawEnabled)
        {
            IsGUI = isGUI;
            _components = new List<Component>();
            _newComponents = new List<Component>();
            _removingComponents = new List<Component>();
            UpdateEnabled = true;
            DrawEnabled = true;
        }

        #endregion


        #region Public Methods

        /// <summary>
        /// True if my bounds collide with the bounds of the given gameobject
        /// </summary>
        public bool CollidesWith(GameObject other)
        {
            return this.Bounds.Intersects(other.Bounds);
        }

        public void AddComponent(Component component)
        {
            _newComponents.Add(component);
            _componentsChanged = true;
        }

        public void AddComponent<T>() where T : Component, new()
        {
            AddComponent(new T());
        }

        public void AddComponents(IEnumerable<Component> components)
        {
            _newComponents.AddRange(components);
            _componentsChanged = true;
        }

        public IEnumerable<T> GetComponents<T>()
        {
            return _components.OfType<T>();
        }

        public void RemoveComponent(Component component)
        {
            _removingComponents.Add(component);
            _componentsChanged = true;
        }

        public void RemoveComponents(IEnumerable<Component> components)
        {
            _removingComponents.AddRange(components);
            _componentsChanged = true;
        }

        public void RemoveComponents<T>() where T : Component
        {
            _removingComponents.AddRange(_components.OfType<T>());
            _componentsChanged = true;
        }

        public override int GetHashCode()
        {
            return this.GetType().GetHashCode();
        }

        #endregion


        #region Protected Methods

        /// <summary>
        /// Perform update logic before components
        /// </summary>
        protected virtual void UpdateBeforeComponents(GameTime gameTime)
        {
            //do nothing
        }

        /// <summary>
        /// Perform update logic after components
        /// </summary>
        protected virtual void UpdateAfterComponents(GameTime gameTime)
        {
            //do nothing
        }

        /// <summary>
        /// Perform draw calls that are not handled by components
        /// </summary>
        protected virtual void DrawObject()
        {
            //do nothing
        }

        /// <summary>
        /// Called whenever the Bounds have changed
        /// </summary>
        protected virtual void OnMove()
        {
            //do nothing
        }

        #endregion


        #region Private Methods
        
        private void UpdateAll(GameTime gameTime)
        {
            if (_componentsChanged)
            {
                _newComponents.ForEach(c => c.Parent = this);
                _components = _components.Concat(_newComponents)
                                         .Except(_removingComponents)
                                         .ToList();
                _componentsChanged = false;
            }

            UpdateBeforeComponents(gameTime);

            foreach (Component c in _components)
            {
                c.Update(gameTime);
            }

            UpdateAfterComponents(gameTime);

            if (PreviousBounds != Bounds)
            {
                OnMove();
                PreviousBounds = Bounds;
            }
        }

        private void UpdateNone(GameTime gameTime)
        {
            //Do nothing
        }

        private void DrawAll()
        {
            DrawObject();

            foreach (Component c in _components)
            {
                c.Draw();
            }
        }

        private void DrawNone()
        {
            //Do nothing
        }

        #endregion

    }
    
}
